/*
  Copyright (C) 2011, Kyungjoo Kim
  
  This file is part of LINAL (LINear ALgebra)
  
  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  Also add information on how to contact you by electronic and paper mail.
  
  
  Kyungjoo Kim
  iamkyungjoo@gmail.com
*/
#ifndef LINAL_FLAT_HXX
#define LINAL_FLAT_HXX

namespace linal {
  typedef struct FLA_Obj_view FLA_Obj;
  typedef class  Matrix_*     Matrix;
  typedef class  Flat_*       Flat;

  /*!
    Flat_ class is specialized for scalar operations.
  */
  class Flat_ : public Matrix_ {
  private:
  protected:
  public:
    Flat_() : Matrix_() { }
    Flat_(FLA_Obj obj) : Matrix_(obj) { }
    Flat_(const Flat_&b) : Matrix_(b) { }

    virtual ~Flat_() { this->free(); }

    // ---------------------------------------------------------
    // ** Create / Free
    void create(Int_ type, Int_ m, Int_ n) { Matrix_::create(type, FLA_SCALAR, m, n); }
    void create(Int_ trans, Flat_ b) {
      LINAL_ERROR( check_trans_all( trans ), LINAL_ERROR_CHECK_TRANS );
      switch (trans) {
      case LINAL_NO_TRANSPOSE: create(b.get_datatype(), b.get_m(), b.get_n());break;
      case LINAL_TRANSPOSE:    create(b.get_datatype(), b.get_n(), b.get_m());break;
      }
    }
    void wrap(FLA_Obj &fla)  {
      LINAL_ERROR( fla.base != NULL && 
                   fla.base->elemtype == FLA_SCALAR, 
                   LINAL_ERROR_CHECK_SCALAR_ELEMTYPE );
      Matrix_::wrap(fla);
    }

    void create_without_buffer(Int_ type, Int_ m, Int_ n) {
      if (!m || !n) return;

      LINAL_ERROR( !this->is_created(),         LINAL_ERROR_OBJ_CREATED );
      LINAL_ERROR( check_all_scalar_type(type), LINAL_ERROR_CHECK_SCALAR );

      FLA_Obj_create_without_buffer(type, m, n, &(this->_fla));
      this->set_created(1);
    }

    // following two methods are user responsibilities 
    // fla base object is modified 
    void attach_buffer(Int_ cs, Int_ rs, void *buffer) {
      LINAL_ERROR( this->is_created(), ">> Not created yet");
      FLA_Obj_attach_buffer(buffer, rs, cs, &(this->_fla));
    }
    void free_without_buffer() {
      if (!this->is_created()) 
        return;
      FLA_Obj_free_without_buffer(&(this->_fla));
      this->set_created(0);
    }

    void set_identity() {
      if (this->is_created())
        FLA_Set_to_identity(this->get_fla());
    }

    void hermitianize(Int_ uplo) { 
      if (this->is_created())
        FLA_Hermitianize( uplo, this->get_fla() );
    }
    void transpose() { 
      if (this->is_created())
        FLA_Transpose( this->get_fla() );
    }

    void set_scalar(Flat_ &alpha, Int_ row, Int_ col) {
      Flat_ beta;
      Matrix_::extract(beta, 1, 1, row, col);
      FLA_Set( ~alpha, ~beta );
    }

    void get_scalar(Flat_ &alpha, Int_ row, Int_ col) {
      Matrix_::extract(alpha, 1, 1, row, col);
    }

    template<class T_> std::ostream& elem_disp(std::ostream& os) const {
      if (this->is_buffer_null()) 
        return (os << "Buffer is null " << std::endl);
      
      Int_ cs = this->get_cs(), m = this->get_m(), n = this->get_n();
      void *buffer = this->get_buffer();
      
      for (Int_ k1=0;k1<this->get_m();++k1) {
        for (Int_ k2=0;k2<this->get_n();++k2) {
          T_ val = elem<T_>(buffer, cs, 1, k1, k2);
          os << val << "  ";
        }
        os << std::endl;
      }
      return (os << std::endl);
    }

    virtual std::ostream& content(std::ostream& os) const {
      this->Matrix_::content(os);
      if (this->is_buffer_null()) 
        return (os << "Buffer is null " << std::endl);
      
      switch(this->get_datatype()) {
      case LINAL_SINGLE_REAL:    elem_disp<Float_ > (os);  break;
      case LINAL_DOUBLE_REAL:    elem_disp<Double_ >(os);  break;
      case LINAL_SINGLE_COMPLEX: elem_disp<fCmplx_ >(os);  break;
      case LINAL_DOUBLE_COMPLEX: elem_disp<dCmplx_ >(os);  break;  
      case LINAL_INT:            elem_disp<Int_ >   (os);  break;  
      }

      return ( os << std::endl );
    }
  };
}

#endif
